<!--
 树形结构数据展示
 @Author: mosowe
 @Date:2023-05-11 11:17:58
-->
<template>
  <view
    class=""
    v-if="treeData.length">
    <view
      class="mosowe-tree-list-component"
      v-for="(item, index) in treeData"
      :key="index">
      <view class="mosowe-tree-title-wrap">
        <view class="mosowe-tree-title-icon">
          <view
            class="icon"
            v-if="item[children] && item[children].length"
            :class="{
              open: showChidren.includes(item[nodeKey]),
              close: !showChidren.includes(item[nodeKey])
            }"
            @click="toggleChildrens(item[nodeKey])"></view>
        </view>
        <view
          class="mosowe-tree-title-selection"
          :class="{
            disabled: item.disabled,
            'part-select':
              item[children] &&
              treeToArray(JSON.parse(JSON.stringify(item[children])), children).filter((ci) =>
                selectionList.includes(ci[nodeKey])
              ).length > 0 &&
              !selectionList.includes(item[nodeKey]),
            'all-select': selectionList.includes(item[nodeKey])
          }"
          v-if="multiple"
          @click="selectionClick(item)"></view>
        <view
          class="mosowe-tree-title-name"
          @click.stop="nodeClick(item)">
          <!-- #ifndef MP -->
          <slot :node="item">
            {{ item[text] }}
          </slot>
          <!-- #endif -->
          <!-- #ifdef MP -->
          <!-- 小程序不支持此类插槽，需要自行处理相关结构在此处 -->
          {{ item[text] }}
          <!-- #endif -->
        </view>
      </view>
      <template v-if="item[children] && item[children].length">
        <template v-if="showChidren.indexOf(item[nodeKey]) > -1">
          <view class="mosowe-tree-children-wrap">
            <mosowe-tree-list
              v-model="selectionList"
              :treeData="item[children]"
              :text="text"
              :children="children"
              :multiple="multiple"
              :nodeKey="nodeKey"
              :defaultShowChildren="defaultShowChildren"
              :textClickToggle="textClickToggle"
              :accordion="accordion"
              :index="index + 1"
              :isOnceShow="onceShow"
              @nodeClick="nodeClick"
              @childCancel="childCancel(item)"
              @childChoosed="childChoosed(item)">
              <!-- #ifndef MP -->
              <template #default="{ node }">
                <slot :node="node">
                  {{ node[text] }}
                </slot>
              </template>
              <!-- #endif -->
            </mosowe-tree-list>
          </view>
        </template>
      </template>
    </view>
  </view>
</template>

<script>
export default {
  name: 'mosowe-tree-list',
  emits: ['nodeClick', 'childCancel', 'childChoosed', 'update:modelValue'],
  props: {
    modelValue: {
      type: Array,
      default: () => []
    },
    value: {
      type: Array,
      default: () => []
    },
    treeData: {
      // 数据
      type: Array,
      default: () => []
    },
    text: {
      // 显示的文案标题字段
      type: String,
      default: 'text'
    },
    nodeKey: {
      // 目标字段
      type: String,
      default: 'value'
    },
    children: {
      // 子集字段
      type: String,
      default: 'childrens'
    },
    multiple: {
      // 显示多选框
      type: Boolean,
      default: false
    },
    textClickToggle: {
      // 标题点击触发子集列表显隐
      type: Boolean,
      default: false
    },
    defaultShowChildren: {
      // 默认展开所有，或展开几级
      type: [Boolean, Number],
      default: false
    },
    accordion: {
      // 手风琴模式
      type: Boolean,
      default: false
    },
    index: {
      // 当前层级
      type: Number,
      default: 1
    },
    isOnceShow: {
      // 是否显示过了
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      showChidren: [],
      once: false,
      onceShow: false
    };
  },
  computed: {
    selectionList: {
      get() {
        let list = [];
        // TODO 兼容 vue2
        // #ifdef VUE2
        list = this.value;
        // #endif

        // TODO　兼容　vue3
        // #ifdef VUE3
        list = this.modelValue;
        // #endif
        if (!this.once && list.length !== 0) {
          // 只执行一次，初始化时
          const flatArr = this.getValueByKeyForObject({
            value: list,
            source: this.treeData,
            valueKey: this.nodeKey,
            targetKey: this.children,
            treeDataAll: false,
            treeChildKey: this.children
          })
            .flat(Infinity)
            .filter((item) => item)
            .map((item) => {
              return item[this.nodeKey];
            });

          this.once = true;
          const r = [...list, ...flatArr];
          return [...new Set(r)];
        }

        return list;
      },
      set(value) {
        // TODO 兼容 vue2
        // #ifdef VUE2
        this.$emit('input', value);
        // #endif

        // TODO　兼容　vue3
        // #ifdef VUE3
        this.$emit('update:modelValue', value);
        // #endif
      }
    }
  },
  watch: {
    defaultShowChildren: {
      handler() {
        if (!this.isOnceShow) {
          if (this.defaultShowChildren === true) {
            this.showChidren = this.treeToArray(
              JSON.parse(JSON.stringify(this.treeData)),
              this.children
            ).map((item) => item[this.nodeKey]);
          } else if (typeof this.defaultShowChildren === 'number') {
            if (this.index < this.defaultShowChildren) {
              const findIndex = (treeData, index) => {
                let r = [];
                treeData.forEach((item) => {
                  r.push(item[this.nodeKey]);
                  if (
                    index < this.defaultShowChildren &&
                    item[this.children] &&
                    item[this.children].length
                  ) {
                    r.push(...findIndex(item[this.children], index + 1));
                  }
                });
                return r;
              };
              const list = findIndex(JSON.parse(JSON.stringify(this.treeData)), 0);
              this.showChidren = list;
            }
          }
          let t = setTimeout(() => {
            clearTimeout(t);
            t = null;
            this.onceShow = this.index === 1 ? true : this.isOnceShow;
          }, 10);
        }
      },
      immediate: true
    }
  },
  methods: {
    nodeClick(e) {
      this.$emit('nodeClick', e);
      console.log(e);
      if (this.textClickToggle) {
        this.toggleChildrens(e[this.nodeKey]);
      }
    },
    // 选择
    selectionClick(node) {
      const list = [...this.selectionList];
      const value = node[this.nodeKey];
      if (node[this.children] && node[this.children].length) {
        // 此项有子集
        if (list.includes(value)) {
          // 已选->改取消选择
          const index = list.findIndex((item) => item === value);
          list.splice(index, 1);
          // 子集的也要取消掉
          const childrenData = JSON.parse(JSON.stringify(node[this.children]));
          this.treeToArray(childrenData, this.children).forEach((item) => {
            const findex = list.findIndex((fitem) => fitem === item[this.nodeKey]);
            list.splice(findex, 1);
          });
          // 父级也要取消
          this.$emit('childCancel');
        } else {
          // 加入已选
          list.push(value);
          // 子集的也要全部加入
          const childrenData = JSON.parse(JSON.stringify(node[this.children]));
          this.treeToArray(childrenData, this.children).forEach((item) => {
            list.push(item[this.nodeKey]);
          });
          // 父级看情况选择不选择
          this.$emit('childChoosed');
        }
      } else {
        // 没有子集
        if (list.includes(value)) {
          // 已选->改取消选择
          const index = list.findIndex((item) => item === value);
          list.splice(index, 1);
          // 父级也要取消
          this.$emit('childCancel');
        } else {
          // 加入已选
          list.push(value);
          // 父级看情况选择不选择
          this.$emit('childChoosed');
        }
      }

      this.selectionList = [...new Set(list)];
    },
    childChoosed(node) {
      let t = setTimeout(() => {
        clearTimeout(t);
        t = null;
        const list = [...this.selectionList];
        const value = node[this.nodeKey];
        let join = true;
        node[this.children].forEach((item) => {
          if (!list.includes(item[this.nodeKey])) {
            join = false;
          }
        });
        if (join) {
          list.push(value);
        }
        // 父级也要考虑下选择不
        this.selectionList = [...new Set(list)];
        this.$emit('childChoosed');
      }, 10);
    },
    childCancel(node) {
      let t = setTimeout(() => {
        clearTimeout(t);
        t = null;
        const list = [...this.selectionList];
        const value = node[this.nodeKey];
        const index = list.findIndex((item) => item === value);
        if (index > -1) {
          list.splice(index, 1);
          this.selectionList = [...new Set(list)];
        } // 父级也要取消
        this.$emit('childCancel');
      }, 10);
    },
    // 展开收起
    toggleChildrens(key) {
      if (this.showChidren.includes(key)) {
        const index = this.showChidren.findIndex((item) => item === key);
        this.showChidren.splice(index, 1);
      } else {
        if (this.accordion) {
          this.showChidren = [key];
        } else {
          this.showChidren.push(key);
        }
      }
    },
    // 树转数组
    treeToArray(nodes, childKey) {
      var r = [];
      if (Array.isArray(nodes)) {
        for (var i = 0, l = nodes.length; i < l; i++) {
          r.push(nodes[i]); // 取每项数据放入一个新数组
          if (Array.isArray(nodes[i][childKey]) && nodes[i][childKey].length > 0)
            // 若存在leaf则递归调用，把数据拼接到新数组中，并且删除该leaf
            r = r.concat(this.treeToArray(nodes[i][childKey]));
          delete nodes[i][childKey];
        }
      }
      return r;
    },
    getValueByKeyForObject(options) {
      if (!options) {
        return [];
      }
      const {
        value,
        source,
        valueKey,
        targetKey,
        treeDataAll = false,
        treeChildKey = 'leaf'
      } = options;
      let treePrefix = '';
      let result = [];
      if (!(value ?? '') || !source || !valueKey || !targetKey) {
        requiredKey(options, ['value', 'source', 'valueKey', 'targetKey']);
        return [];
      }
      let val = value;
      if (typeof value === 'string') {
        val = [value];
      }
      // 树数据处理
      const treeDeal = (data) => {
        if (data[treeChildKey]) {
          // 是个树
          let r = this.getValueByKeyForObject({
            ...options,
            source: data[treeChildKey]
          });
          if (treeDataAll && r.length) {
            // 需要返回父级数据
            treePrefix = data[targetKey];
            r = r.map((item) => {
              return treePrefix + '/' + item;
            });
          }
          result.push(...r);
        }
      };
      if (Object.prototype.toString.call(source) === '[object Object]') {
        // object类型，假设是树
        if (val.includes(source[valueKey])) {
          result.push(source[targetKey]);
        }
        treeDeal(source);
      } else {
        // 数组，也可能是数组树
        source.forEach((item) => {
          if (Object.prototype.toString.call(item) === '[object Object]') {
            // object类型，假设是树
            if (val.includes(item[valueKey])) {
              result.push(item[targetKey]);
            }
            treeDeal(item); // 树处理
          }
          if (Array.isArray(item)) {
            // 多维数组
            result.push(
              ...this.getValueByKeyForObject({
                ...options,
                source: item
              })
            );
          }
        });
      }
      return result;
    },
    requiredKey(obj, keys) {
      let r = [];
      keys.forEach((item) => {
        if (ZoIsEmpty(obj[item])) {
          console.error(`${item} is required!`);
          r.push(item);
        }
      });
      return r;
    }
  }
};
</script>

<style scoped lang="scss">
.mosowe-tree-list-component {
  font-size: 14px;
  color: #333;
  line-height: 24px;
  .mosowe-tree-title-wrap {
    display: flex;
    align-items: center;
    .mosowe-tree-title-icon {
      flex: none;
      width: 20px;
      height: 20px;
      .icon {
        width: 100%;
        height: 100%;
        display: flex;
        position: relative;
        cursor: pointer;
        transition: all 0.3s;
        &::before {
          content: '';
          display: inline-block;
          width: 0;
          height: 0;
          border: 6px solid;
          border-color: transparent transparent transparent #999;
          margin: auto;
          margin-right: -2rpx;
        }
        &.open {
          transform: rotate(90deg);
          transform-origin: center;
        }
      }
    }
    .mosowe-tree-title-selection {
      flex: none;
      width: 12px;
      height: 12px;
      border: 1px solid #ccc;
      border-radius: 2px;
      margin-right: 4px;
      cursor: pointer;
      position: relative;
      &.part-select {
        background-color: $uni-color-primary;
        border-color: $uni-color-primary;
        &::after {
          content: '';
          display: block;
          width: 8px;
          height: 2px;
          background-color: #fff;
          border-radius: 2px;
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translateX(-50%) translateY(-50%);
        }
      }
      &.all-select {
        background-color: $uni-color-primary;
        border-color: $uni-color-primary;
        &::after {
          content: '';
          display: block;
          width: 6px;
          height: 3px;
          border: 2px solid;
          border-color: transparent transparent #fff #fff;
          border-radius: 2px;
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translateX(-50%) translateY(-80%) rotate(-45deg);
        }
      }
      &.disabled {
        background-color: rgba(0, 0, 0, 0.2);
        cursor: no-drop;
      }
    }
    .mosowe-tree-title-name {
      flex: 1;
      cursor: pointer;
    }
  }
  .mosowe-tree-children-wrap {
    padding-left: 10px;
  }
}
</style>
